'''
Created on Mar 23, 2013

@author: Petko
'''
from django.views.generic.base import View
from sent.utils import CommonResult, ErrorIds, createValidToken, AccessToken
from sent.utils.result_printer import getPrinter

from sent.model.entities.opinion import Opinion, OpinionState
from sent.model.OpinionDBManager import OpinionDBManager
from sent.utils.permissions import parseScopeList
import json

class GetOpinion(View):
    '''
    classdocs
    '''


    def __init__(self):
        '''
        Constructor
        '''
        pass
    
    def buildOpinionTree(self, rootOp, subop_list, subop_index = 0):
        '''
        This method receives results from multi-level depth first listing of opinions inside rootOp, and builds the opinion tree.
        This is a recursive function. The function returns the position inside the list where the opinions are not anymore subopinions
        of rootOp (or the end of the list)
        @param rootOp: The opinion inside which we are storing the current list
        @param subop_list: A list with subopinions to fill the tree
        @param subop_index: Index inside subop_list
        '''
        assert isinstance(rootOp, Opinion)
        rootOp.subOpinions = []
        while subop_index < len(subop_list) and subop_list[subop_index].tree_depth > rootOp.tree_depth:
            currentOp = subop_list[subop_index]
            if currentOp.tree_depth == rootOp.tree_depth + 1:
                #if the opinion is a direct child - add it to the subopinions
                rootOp.subOpinions.append(currentOp)
                lastAddedChild = currentOp
                subop_index += 1
            else:
                assert lastAddedChild
                subop_index = self.buildOpinionTree(lastAddedChild, subop_list, subop_index)
        return subop_index
    
    def buildOpinionTreeBreadthFirst(self, op, subopinions):
        #The results from the first level are first, then follow the results from the second level(s) etc.
        #At each level the results are ordered by "order" which must be preserved
        #Luckily I have a parent_id for each opinion so I can bring everything into order with almost linear complexity
        parentsDict = {op.getUUID() : op}
        op.subOpinions = []
        for subop in subopinions:
            parent = parentsDict[subop.getParentUUID()]
            
            parent.subOpinions.append(subop)
            
            if not parentsDict.has_key(subop.getUUID()):
                subop.subOpinions = []
                parentsDict[subop.getUUID()] = subop
    
    def fillSubopinions(self, dbMan, op, subop_desc):
        assert isinstance(subop_desc, dict)
        assert isinstance(op, Opinion)
        tree_order = subop_desc.get('tree_order', None)
        order = subop_desc.get('order', None)
        evaluation_order = subop_desc.get('evaluation_order', 'none')
        
        order_hierarchically = subop_desc.get('order_hierarchically', 1)
        if order_hierarchically == '0':
            order_hierarchically = 0
            
        skip_empty = subop_desc.get('skip_empty', 1)
        if skip_empty == '0':
            skip_empty = 0
            
        try:
            max_depth = int(subop_desc.get('max_depth', -1))
            max_content_length = int(subop_desc.get('max_content_length', 0))
            first_subopinion = int(subop_desc.get('first_subopinion', 0))
            max_subopinions = int(subop_desc.get('max_subopinions', 10))
        except ValueError:
            return False
        
        subsubop_desc = subop_desc.get('subopinions_desc', None)
        
        if tree_order and order and max_depth >= 0:
            if tree_order == 'depth_first':
                if subsubop_desc:
                    assert isinstance(subsubop_desc, dict)
                    if max_depth == 1:
                        subopinions = dbMan.getSubOpinions(op, tree_order, order, max_depth, first_subopinion, max_subopinions, evaluation_order, skip_empty)
                        
                        subopinions_to_fill_recursively = []
                        for subop in subopinions:
                            assert isinstance(subop, Opinion)
                            if subop.getTotalSubopinions() > 0:
                                #add for recursive call only if the sub-opinion has sub-sub-opinions
                                subopinions_to_fill_recursively.append(subop)
                        
                        try:
                            subsubop_max_depth = int(subsubop_desc.get('max_depth', -1))
                            first_subsubopinion = int(subsubop_desc.get('first_subopinion', 0))
                            max_subsubopinions = int(subsubop_desc.get('max_subopinions', 10))
                            max_subsubopinions_content_length = int(subsubop_desc.get('max_content_length', 0))
                        except ValueError:
                            return False
                        subsubop_tree_order = subsubop_desc.get('tree_order', None)
                        subsubop_order = subsubop_desc.get('order', None)
                        subsubop_evaluation_order = subsubop_desc.get('evaluation_order', '')
                        
                        subsubop_order_hierarchically = subsubop_desc.get('order_hierarchically', 1)
                        if subsubop_order_hierarchically == '0':
                            subsubop_order_hierarchically = 0
                        
                        subsubop_skip_empty = subsubop_desc.get('skip_empty', 1)
                        if subsubop_skip_empty == '0':
                            subsubop_skip_empty = 0
                            
                        if subsubop_tree_order and subsubop_max_depth >= 0 and subsubop_order:
                            
                            if (subsubop_tree_order != 'depth_first' or subsubop_max_depth > 1) and first_subsubopinion == 0:
                                #Scan for sub-opinions whose queries can be optimized by fetching all their children in a single query.
                                #These sub-opinions are ones we know that all their children will be fetched anyways
                                #The optimization is done with a single query that fetches the opinions whose nested_set_left value
                                #"says" that this opinion belongs to some of the "optimizable" sub-opinions
                                new_subopinions_to_fill_recursively = []
                                optimized_query_ranges = []
                                for op_to_optimize in subopinions_to_fill_recursively:
                                    assert isinstance(op_to_optimize, Opinion)
                                    if op_to_optimize.getTotalSubopinions() <= max_subsubopinions:
                                        #This sub-opinion will be optimized
                                        optimized_query_ranges.append((op_to_optimize.nested_set_left + 1, op_to_optimize.nested_set_right - 1))
                                    else:
                                        #this sub-opinion will not be optimized
                                        new_subopinions_to_fill_recursively.append(op_to_optimize)
                                        
                                #save the opinions that cannot be optimized in subopinions_to_fill_recursively
                                subopinions_to_fill_recursively = new_subopinions_to_fill_recursively
                                
                                if len(optimized_query_ranges):
                                    #now load the sub-sub-opinions with a single query
                                    optimized_subsubopinions = dbMan.getBatchOpinions(op.base_op_id, optimized_query_ranges, 
                                                                                      op.tree_depth + 2, op.tree_depth + 1 + subsubop_max_depth,
                                                                                      subsubop_skip_empty)
                                    
                                    #fill the optimized sub-sub-opinions texts
                                    dbMan.fillOpinionTexts(optimized_subsubopinions, max_subsubopinions_content_length)
                                    
                                    #set in opinions
                                    for subop in subopinions:
                                        subop.subOpinions = []
                                        for optimized_subsub in optimized_subsubopinions:
                                            if optimized_subsub.nested_set_left > subop.nested_set_left \
                                                and optimized_subsub.nested_set_right < subop.nested_set_right:
                                                subop.subOpinions.append(optimized_subsub)
                                        
                                        if len(subop.subOpinions):
                                            if subsubop_tree_order == 'depth_first':
                                                #in-memory sort
                                                if subsubop_order == 'newest':
                                                    subop.subOpinions.sort(key=lambda opin_obj: opin_obj.nested_set_right, reverse=True)
                                                elif subsubop_order == 'oldest':
                                                    subop.subOpinions.sort(key=lambda opin_obj: opin_obj.nested_set_left)
                                                else:
                                                    #raise AssertionError("With depth_first, if max_depth is > 1, sorting only by creation time is supported. Current sorting: %s" % subsubop_order)
                                                    return False
                                                
                                                #in-memory reorder
                                                self.buildOpinionTree(subop, subop.subOpinions)
                                            else:
                                                #in-memory sort
                                                if subsubop_order == 'newest':
                                                    subop.subOpinions.sort(key=lambda opin_obj: opin_obj.creation_time, reverse=True)
                                                elif subsubop_order == 'oldest':
                                                    subop.subOpinions.sort(key=lambda opin_obj: opin_obj.creation_time)
                                                elif subsubop_order == 'most_positive':
                                                    subop.subOpinions.sort(key=lambda opin_obj: opin_obj.positive_attention, reverse=True)
                                                elif subsubop_order == 'most_negative':
                                                    subop.subOpinions.sort(key=lambda opin_obj: opin_obj.negative_attention, reverse=True)
                                                elif subsubop_order == 'most_attended':
                                                    subop.subOpinions.sort(key=lambda opin_obj: opin_obj.nested_set_right - opin_obj.nested_set_left, reverse=True)
                                                elif subsubop_order == 'general_attention_desc':
                                                    subop.subOpinions.sort(key=lambda opin_obj: opin_obj.positive_attention - opin_obj.negative_attention, reverse=True)
                                                else:
                                                    return False
                                                
                                                if subsubop_evaluation_order == 'positive':
                                                    subop.subOpinions.sort(key=lambda opin_obj: opin_obj.evaluation, reverse=True)
                                                elif subsubop_evaluation_order == 'negative':
                                                    subop.subOpinions.sort(key=lambda opin_obj: opin_obj.evaluation)
                                                
                                                if subsubop_tree_order == 'breadth_first':
                                                    subop.subOpinions.sort(key=lambda opin_obj: opin_obj.tree_depth)
                                                    
                                                    if subsubop_order_hierarchically:
                                                        self.buildOpinionTreeBreadthFirst(subop, list(subop.subOpinions))
                                                    
                            #for those which were not optimized: call fillSubopinions recursively
                            for subop_to_fill in subopinions_to_fill_recursively:
                                assert subop_to_fill.subOpinions == None or len(subop_to_fill.subOpinions) == 0
                                if not self.fillSubopinions(dbMan, subop_to_fill, subsubop_desc):
                                    return False
                        else:
                            #invalid sub-sub-opinion descriptor
                            return False
                    else:
                        return False
                else:
                    if order == 'newest' or order == 'oldest' or max_depth == 1:
                        subopinions = dbMan.getSubOpinions(op, tree_order, order, max_depth, first_subopinion, max_subopinions, evaluation_order, skip_empty)
                    else:
                        return False
                pass
            else:
                subopinions = dbMan.getSubOpinions(op, tree_order, order, max_depth, first_subopinion, max_subopinions, evaluation_order, skip_empty)
                
            
            if subopinions == None:
                return False
            else:
                dbMan.fillOpinionTexts(subopinions, max_content_length)
                
                if tree_order == 'depth_first' and max_depth > 1:
                    if first_subopinion > 0 and len(subopinions) > 0 and subopinions[0].parent_id != op.id:
                        #get the path from the first result to the opinion...
                        pathToOp = dbMan.getPathFromOpToOp(op, subopinions[0])
                        
                        dbMan.fillOpinionTexts(pathToOp, max_content_length)
                         
                        #... and insert the path at the beginning so that the tree order is preserved
                        
                        subopinions = pathToOp + subopinions
                    self.buildOpinionTree(op, subopinions)
                elif tree_order == 'breadth_first':
                    if order_hierarchically:
                        self.buildOpinionTreeBreadthFirst(op, subopinions)
                    else:
                        op.subOpinions = subopinions
                else:
                    op.subOpinions = subopinions
                return True
        else:
            return False
    
    def post(self, *args, **kwargs):
        return self.get()
    
    def get(self, *args, **kwargs):
        
        cResult = CommonResult()
        
        accessTokenStr = self.request.REQUEST.get('access_token', None)
        opId = self.request.REQUEST.get('opinion_id', None)
        subopinions_desc = self.request.REQUEST.get('subopinions_desc', None)
        subopinions_only = self.request.REQUEST.get('subopinions_only', False)
        load_current_opinion_data = (not subopinions_only) or subopinions_only == "0"
        
        try:
            maxContentLength = int(self.request.REQUEST.get('max_content_length', 0))
            if subopinions_desc:
                subopinions_desc = json.loads(subopinions_desc)
        except ValueError:
            cResult.errorId = ErrorIds.BADREQUEST
            cResult.result = 'Bad format'
            return getPrinter().printResult(cResult)
        
        if cResult.errorId == ErrorIds.SUCCESS and opId:
            
            dbMan = OpinionDBManager(True)
            try:
                op = dbMan.get_opinion_by_id_str(opId, loadCreator = load_current_opinion_data, allowed_states=[OpinionState.NORMAL, OpinionState.WAITING_UPDATE])
                
                if op:
                    assert isinstance(op, Opinion)
                    
                    if accessTokenStr:
                        accessToken, error = createValidToken(accessTokenStr)
                    else:
                        if op.containsDefaultScope('r'):
                            accessToken = AccessToken()
                            accessToken.scopesSet = parseScopeList('all:r')
                        else:
                            error = "Missing 'access_token' parameter"
                            
                    if accessToken:
                        assert isinstance(accessToken, AccessToken)
                        if accessToken.containsScope(str(op.getBaseUUID()) + ':r') \
                            or ( accessToken.containsScope('all:r') \
                                     and op.containsDefaultScope('r') ):
                            
                            cResult.errorId = ErrorIds.SUCCESS
                            
                            if subopinions_desc:
                                if not self.fillSubopinions(dbMan, op, subopinions_desc):
                                    cResult.errorId = ErrorIds.BADREQUEST
                                    cResult.result = "Wrong or missing value in subopinions_desc"
                            
                            if cResult.errorId == ErrorIds.SUCCESS:
                                
                                if load_current_opinion_data:
                                    dbMan.fillOpinionTexts([op], maxContentLength)
                                
                                #op.totalSubopinions = (op.nested_set_right - op.nested_set_left - 1) / 2
                                
                                cResult.result = op.toPrimitiveDict()
                        else:
                            cResult.errorId = ErrorIds.UNAUTHORIZED
                            cResult.result = "No 'r' permissions for this base opinion"
                    else:
                        cResult.errorId = ErrorIds.UNAUTHORIZED
                        cResult.result = error
                else:
                    cResult.errorId = ErrorIds.NOT_FOUND
                    cResult.result = "Opinion %s not found" % opId
            except ValueError:
                cResult.errorId = ErrorIds.BADREQUEST
                cResult.result = 'Bad format'
            finally:
                dbMan.closeSession()
        else:
            cResult.errorId = ErrorIds.BADREQUEST
            cResult.result = "Missing parameter"
            
        return getPrinter().printResult(cResult)
    
    